#include "lua_bind.hh"
#include "lua_helper.hh"
#include "../../protocol_files/common/generated_proto_header.hh"
#include "../../protocol_files/lua_rpc/rpc_generated.hh"
#include "../../protocol_files/lua_rpc/lua_bind_rpc_generated.hh"
#include "../../plugin/base/rpc_base.hh"
#include "lua_entity.hh"

static const luaL_Reg lua_c_methods[] = {
    { "sleep", &LuaBinder::sleep },
    { "newId", &LuaBinder::uuid },
    { "findPath", &LuaBinder::findPath },
    { "closePath", &LuaBinder::closePath },
    { "getEntity", &LuaBinder::findEntity },
    { "ret", &LuaBinder::ret },
    { "abort", &LuaBinder::abort },
    { "verbose", &LuaBinder::verbose },
    { "debug", &LuaBinder::debug },
    { "warning", &LuaBinder::warning },
    { "error", &LuaBinder::error },
    { "fatal", &LuaBinder::fatal },
    { "create", &LuaBinder::create },
    { "spawn", &LuaBinder::spawn },
    { "resume", &LuaBinder::resume },
    { "yield", &LuaBinder::yield },
    { "close", &LuaBinder::close },
    { nullptr, nullptr }
};

template <typename...ARGS>
inline bool callLuaFunctionNoRet(const char* name, lua_State* L, ARGS...args) {
    lua_State* co = L;
    if (!L) {
        co = lua_newthread(LuaRef.getVirtualMachine());
    }
    lua_getglobal(co, name);
    std::tuple<ARGS...> tupleArg = std::make_tuple(args...);
    tuple_foreach(tupleArg, PushMethod(co));
    auto ret = lua_resume(co, nullptr, std::tuple_size<std::tuple<ARGS...>>::value);
    return catchError(co, ret);
}

bool LuaBinder::onInstall() {
    virtualMachine_ = luaL_newstate();
    if (!virtualMachine_) {
        return false;
    }
    installLibrary();
    return true;
}

bool LuaBinder::onUninstall() {
    if (virtualMachine_) {
        lua_close(virtualMachine_);
    }
    virtualMachine_ = nullptr;
    return true;
}

void LuaBinder::installLibrary() {
    StackBalancer sb(virtualMachine_);
    luaL_openlibs(virtualMachine_);
    installCModule(virtualMachine_, "C", lua_c_methods);
    LuaEntity::installEntityLibrary(virtualMachine_);
    LuaRPC::installRPCProxyLibrary();
    installCoTable(virtualMachine_);
    mainCo_.reset(CoInfo::newThread());
    tickCo_.reset(CoInfo::newThread());
    startCo_.reset(CoInfo::newThread());
}

bool LuaBinder::onStart() {
    if (start_) {
        return true;
    }
    auto error = luaL_loadfile(*mainCo_, FrameworkRef.getRootScript().c_str());
    if (error) {
        auto s = lua_tostring(*mainCo_, -1);
        fatallog << std::string(s) << endlog;
        return false;
    }
    // Start main thread
    catchError(*mainCo_, lua_resume(*mainCo_, nullptr, 0));
    // Call 'onStart'
    if (callLuaFunctionNoRet("onStart", *startCo_)) {
        start_ = true;
    }
    return start_;
}

bool LuaBinder::onStop() {
    if (!start_) {
        return true;
    }
    // Call 'onStop'
    callLuaFunctionNoRet("onStop", nullptr);
    // Cleanup
    mainCo_ = nullptr;
    tickCo_ = nullptr;
    startCo_ = nullptr;
    userCoManager_.clear();
    pathFinderManager_.clear();
    rpcCallManager_.clear();
    timer_.clear();
    coManager_.clear();
    LuaRPC::uninstallRPCProxyLibrary();
    // Full GC
    lua_gc(virtualMachine_, LUA_GCCOLLECT, 0);
    start_ = false;
    return true;
}

void LuaBinder::onTick(time_t tick) {
    if (!start_) {
        return;
    }
    if (LUA_YIELD==lua_status(*tickCo_)) {
        return;
    }
    lua_getglobal(*tickCo_, "onTick");
    lua_pushinteger(*tickCo_, tick);
    catchError(*tickCo_, lua_resume(*tickCo_, nullptr, 1));
}

void LuaBinder::onPathJoin(PathID pathID, SiteType siteType, UserData userData) {
    if (!start_) {
        return;
    }
    auto serial = (std::uint32_t)userData;
    auto pathFinder = getPathFinderManager().get(serial);
    if (!pathFinder) {
        return;
    }
    if (pathFinder->co) {
        lua_pushinteger(pathFinder->co, pathID);
        catchError(pathFinder->co, lua_resume(pathFinder->co, virtualMachine_, 1));
        getPathFinderManager().remove(serial);
    }
}

void LuaBinder::onPathDisjoin(PathID, SiteType) {
}

void LuaBinder::onFindPathTimeout(SiteType siteType, UserData userData) {
}

void LuaBinder::onEntityFound(std::uint32_t type, const EntityIDVector & ids, std::uint64_t userData) {
    if (!start_) {
        return;
    }
    auto serial = (std::uint32_t)userData;
    auto entityFinder = getEntityFinderManager().get(serial);
    if (!entityFinder) {
        return;
    }
    EntityID entityID = 0;
    if (!ids.empty()) {
        entityID = ids[0];
    }
    if (entityFinder->co) {
        lua_pushinteger(entityFinder->co, entityID);
        if (lua_status(entityFinder->co) == LUA_YIELD) {
            catchError(entityFinder->co, lua_resume(entityFinder->co, virtualMachine_, 1));
        }        
        getEntityFinderManager().remove(serial);
        getEntityFinderManager().setImmeEntityID(entityID);
    }
}
